package me.maven.plugin.remotejar.mojos;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import me.maven.plugin.remotejar.config.HostPort;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author langle__
 * @version 1.0
 */
@Mojo(name = "jar", defaultPhase = LifecyclePhase.PACKAGE)
public class JarMojo extends AbstractMojo {

    @Parameter(property = "filePath", defaultValue = "${project.build.directory}\\${project.build.finalName}.${project.packaging}", required = true)
    private String filePath;

    @Parameter(property = "JsonFilePath", defaultValue = "${project.basedir}\\RemoteJar.json", required = true)
    private String JsonFilePath;

    // 毫秒
    @Parameter(property = "timeout", defaultValue = "3000")
    private int timeout;

    @Parameter(property = "maxConnect", defaultValue = "10")
    private int maxConnect;

    @Parameter(property = "warn", defaultValue = "false")
    private boolean warn;

    @Override
    public void execute() {
        Log log = getLog();

        File file = new File(this.filePath);
        if (!file.exists()) {
            log.info("Jar file not exists: " + file.getAbsolutePath());
            return;
        }

        JSONObject json = new JSONObject();
        File jsonFile = new File(this.JsonFilePath);
        if (!jsonFile.exists()) {
            JSONArray array = new JSONArray();
            JSONObject account = new JSONObject();
            account.put("host", "127.0.0.1");
            account.put("port", 25565);
            account.put("password", "RemoteJar");
            array.add(account);
            json.put("Remote", array);
            String pretty = JSON.toJSONString(json, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat);
            try {
                Files.write(jsonFile.toPath(), pretty.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                log.error("Failed to create JSON file: " + jsonFile.getAbsolutePath(), e);
                return;
            }
        } else {
            try {
                String content = new String(Files.readAllBytes(jsonFile.toPath()), StandardCharsets.UTF_8);
                json = JSON.parseObject(content);
            } catch (IOException e) {
                log.error("Failed to read JSON file: " + jsonFile.getAbsolutePath(), e);
            }
        }

        JSONArray array = json.getJSONArray("Remote");
        int size = array.size();

        if (size == 0) {
            log.info("Remote array is empty");
            return;
        }
        ExecutorService executor = Executors.newFixedThreadPool(size);

        for (int i = 0; i < size; i++) {
            JSONObject object = array.getJSONObject(i);
            String host = object.getString("host");
            Integer port = object.getInteger("port");
            String password = object.getString("password");
            executor.submit(() -> sendFile(file, new HostPort(host, port, password)));
        }

        executor.shutdown();
        while (true) {
            if (executor.isTerminated()) {
                log.info("All tasks completed");
                break;
            }
        }
    }

    private void sendFile(File file, HostPort hostPort) {
        Log log = getLog();

        String fileName = file.getName();
        long fileLength = file.length();

        String host = hostPort.getHost();
        Integer port = hostPort.getPort();
        String password = hostPort.getPassword();

        if (host == null || host.isEmpty() || port == null || password == null || password.isEmpty()) {
            log.warn("host or port or password is empty");
            return;
        }

        int count = 0;
        log.info("Send file to " + host + ":" + port);

        while (count < maxConnect) {
            try (
                    Socket client = new Socket(host, port);
                    DataInputStream input = new DataInputStream(client.getInputStream());
                    DataOutputStream output = new DataOutputStream(client.getOutputStream());
                    BufferedInputStream fileInput = new BufferedInputStream(Files.newInputStream(file.toPath()))
            ) {
                client.setSoTimeout(timeout);

                JSONObject jsonPassword = new JSONObject();
                jsonPassword.put("password", password);
                output.writeUTF(JSON.toJSONString(jsonPassword));

                JSONObject jsonResult = JSON.parseObject(input.readUTF());
                boolean result = jsonResult.getBoolean("result");
                if (!result) {
                    log.warn("Password error");
                    return;
                }

                JSONObject jsonFile = new JSONObject();
                jsonFile.put("name", fileName);
                jsonFile.put("length", fileLength);
                output.writeUTF(JSON.toJSONString(jsonFile));

                byte[] buffer = new byte[8192]; // 8KB buffer
                int bytesRead;
                while ((bytesRead = fileInput.read(buffer)) != -1) {
                    output.write(buffer, 0, bytesRead);
                }

                log.info("[" + host + ":" + port + "]: Send success");

                break;
            } catch (UnknownHostException e) {
                if (warn) {
                    log.warn(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage());
                }
                break;
            } catch (IOException e) {
                if (warn) {
                    log.warn(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage());
                }
            }
            count++;
        }
    }

}
